var defaults = {
  lines: 12,
  length: 7,
  width: 5,
  radius: 10,
  scale: 1.0,
  corners: 1,
  color: '#000',
  fadeColor: 'transparent',
  opacity: 0.25,
  rotate: 0,
  direction: 1,
  speed: 1,
  trail: 100,
  fps: 20,
  zIndex: 2e9,
  className: 'spinner',
  top: '50%',
  left: '50%',
  shadow: 'none',
  position: 'absolute',
};
export class Spinner {
  constructor(opts) {
    if (opts === void 0) {
      opts = {};
    }
    this.opts = Object.assign({}, defaults, opts);
  }
  /**
   * Adds the spinner to the given target element. If this instance is already
   * spinning, it is automatically removed from its previous target by calling
   * stop() internally.
   */
  spin = (target) => {
    var _this = this;
    this.stop();
    this.el = document.createElement('div');
    this.el.className = this.opts.className;
    this.el.setAttribute('role', 'progressbar');
    css(this.el, {
      position: this.opts.position,
      width: 0,
      zIndex: this.opts.zIndex,
      left: this.opts.left,
      top: this.opts.top,
      transform: 'scale(' + this.opts.scale + ')',
    });
    if (target) {
      target.insertBefore(this.el, target.firstChild || null);
    }
    var animator;
    var getNow;
    if (typeof requestAnimationFrame !== 'undefined') {
      animator = requestAnimationFrame;
      getNow = function () {
        return performance.now();
      };
    } else {
      // fallback for IE 9
      animator = function (callback) {
        return setTimeout(callback, 1000 / _this.opts.fps);
      };
      getNow = function () {
        return Date.now();
      };
    }
    var lastFrameTime;
    var state = 0; // state is rotation percentage (between 0 and 1)
    var animate = function () {
      var time = getNow();
      if (lastFrameTime === undefined) {
        lastFrameTime = time - 1;
      }
      state += getAdvancePercentage(time - lastFrameTime, _this.opts.speed);
      lastFrameTime = time;
      if (state > 1) {
        state -= Math.floor(state);
      }
      if (_this.el.childNodes.length === _this.opts.lines) {
        for (var line = 0; line < _this.opts.lines; line++) {
          var opacity = getLineOpacity(line, state, _this.opts);
          _this.el.childNodes[
            line
          ].childNodes[0].style.opacity = opacity.toString();
        }
      }
      _this.animateId = _this.el ? animator(animate) : undefined;
    };
    drawLines(this.el, this.opts);
    animate();
    return this;
  };
  /**
   * Stops and removes the Spinner.
   * Stopped spinners may be reused by calling spin() again.
   */
  stop = () => {
    if (this.el) {
      if (typeof requestAnimationFrame !== 'undefined') {
        cancelAnimationFrame(this.animateId);
      } else {
        clearTimeout(this.animateId);
      }
      if (this.el.parentNode) {
        this.el.parentNode.removeChild(this.el);
      }
      this.el = undefined;
    }
    return this;
  };
}
function getAdvancePercentage(msSinceLastFrame, roundsPerSecond) {
  return (msSinceLastFrame / 1000) * roundsPerSecond;
}
function getLineOpacity(line, state, opts) {
  var linePercent = (line + 1) / opts.lines;
  var diff = state - linePercent * opts.direction;
  if (diff < 0 || diff > 1) {
    diff += opts.direction;
  }
  // opacity should start at 1, and approach opacity option as diff reaches trail percentage
  var trailPercent = opts.trail / 100;
  var opacityPercent = 1 - diff / trailPercent;
  if (opacityPercent < 0) {
    return opts.opacity;
  }
  var opacityDiff = 1 - opts.opacity;
  return opacityPercent * opacityDiff + opts.opacity;
}
/**
 * Tries various vendor prefixes and returns the first supported property.
 */
function vendor(el, prop) {
  if (el.style[prop] !== undefined) {
    return prop;
  }
  // needed for transform properties in IE 9
  var prefixed = 'ms' + prop.charAt(0).toUpperCase() + prop.slice(1);
  if (el.style[prefixed] !== undefined) {
    return prefixed;
  }
  return '';
}
/**
 * Sets multiple style properties at once.
 */
function css(el, props) {
  for (var prop in props) {
    el.style[vendor(el, prop) || prop] = props[prop];
  }
  return el;
}
/**
 * Returns the line color from the given string or array.
 */
function getColor(color, idx) {
  return typeof color === 'string' ? color : color[idx % color.length];
}
/**
 * Internal method that draws the individual lines.
 */
function drawLines(el, opts) {
  var borderRadius = Math.round(opts.corners * opts.width * 500) / 1000 + 'px';
  var shadow = 'none';
  if (opts.shadow === true) {
    shadow = '0 2px 4px #000'; // default shadow
  } else if (typeof opts.shadow === 'string') {
    shadow = opts.shadow;
  }
  var shadows = parseBoxShadow(shadow);
  for (var i = 0; i < opts.lines; i++) {
    var degrees = ~~((360 / opts.lines) * i + opts.rotate);
    var backgroundLine = css(document.createElement('div'), {
      position: 'absolute',
      top: -opts.width / 2 + 'px',
      width: opts.length + opts.width + 'px',
      height: opts.width + 'px',
      background: getColor(opts.fadeColor, i),
      borderRadius: borderRadius,
      transformOrigin: 'left',
      transform: 'rotate(' + degrees + 'deg) translateX(' + opts.radius + 'px)',
    });
    var line = css(document.createElement('div'), {
      width: '100%',
      height: '100%',
      background: getColor(opts.color, i),
      borderRadius: borderRadius,
      boxShadow: normalizeShadow(shadows, degrees),
      opacity: opts.opacity,
    });
    backgroundLine.appendChild(line);
    el.appendChild(backgroundLine);
  }
}
function parseBoxShadow(boxShadow) {
  var regex = /^\s*([a-zA-Z]+\s+)?(-?\d+(\.\d+)?)([a-zA-Z]*)\s+(-?\d+(\.\d+)?)([a-zA-Z]*)(.*)$/;
  var shadows = [];
  for (var _i = 0, _a = boxShadow.split(','); _i < _a.length; _i++) {
    var shadow = _a[_i];
    var matches = shadow.match(regex);
    if (matches === null) {
      continue; // invalid syntax
    }
    var x = +matches[2];
    var y = +matches[5];
    var xUnits = matches[4];
    var yUnits = matches[7];
    if (x === 0 && !xUnits) {
      xUnits = yUnits;
    }
    if (y === 0 && !yUnits) {
      yUnits = xUnits;
    }
    if (xUnits !== yUnits) {
      continue; // units must match to use as coordinates
    }
    shadows.push({
      prefix: matches[1] || '',
      x: x,
      y: y,
      xUnits: xUnits,
      yUnits: yUnits,
      end: matches[8],
    });
  }
  return shadows;
}
/**
 * Modify box-shadow x/y offsets to counteract rotation
 */
function normalizeShadow(shadows, degrees) {
  var normalized = [];
  for (var _i = 0, shadows_1 = shadows; _i < shadows_1.length; _i++) {
    var shadow = shadows_1[_i];
    var xy = convertOffset(shadow.x, shadow.y, degrees);
    normalized.push(
      shadow.prefix +
        xy[0] +
        shadow.xUnits +
        ' ' +
        xy[1] +
        shadow.yUnits +
        shadow.end
    );
  }
  return normalized.join(', ');
}
function convertOffset(x, y, degrees) {
  var radians = (degrees * Math.PI) / 180;
  var sin = Math.sin(radians);
  var cos = Math.cos(radians);
  return [
    Math.round((x * cos + y * sin) * 1000) / 1000,
    Math.round((-x * sin + y * cos) * 1000) / 1000,
  ];
}
